/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package uk.ac.staffs.student.autopilot.packet;

import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Observable;

import org.apache.log4j.Logger;

import uk.ac.staffs.student.autopilot.SerialPortManager;
import uk.ac.staffs.student.autopilot.SerialPortStatus;
import uk.ac.staffs.student.autopilot.data.HciQueue;
import uk.ac.staffs.student.autopilot.views.APHciSwingPanel;

/**
 * Creates new packet objects by listening to SerialPort events and processing
 * those containing data by reading from the InputStream.
 * 
 * Writes new packet objects writing them to the OutputStream
 * 
 * Inbuild packet protocol with each packet start and end identified by '[' ']'
 * Within each square braces is the payload of data with each value separated by
 * a comma. E.G. A Gyro data packet. [1,18,-22,-11,-15]
 * 
 * @author Peter
 */
public class PacketBuilder extends Observable implements
        SerialPortEventListener {

    public static Logger logger = Logger.getLogger(PacketBuilder.class);

    /**
     * Buffered input stream from the port
     */
    private InputStream input;
    /**
     * The output stream to the port
     */
    private OutputStream output;
    /**
     * The current set of bytes read from input
     */
    private final ArrayList<Byte> currBuffer = new ArrayList<Byte>();
    /**
     * The FIFO for incoming comms packets
     */
    HciQueue pktFifo;
    /**
     * Ignore initial comms packets
     */
    private int initcomms = 0;
    /**
     * Comms check counter
     */
    private int commsCheckCtr = 0;
    /**
     * The serial port information and status
     */
    protected SerialPortStatus portStatus;
    /**
     * The SerialPortManager this PacketBuilder is connected to
     */
    protected SerialPortManager portManager;

    /**
     * Default constructor
     */
    public PacketBuilder(final SerialPortManager portManager) {
        this.portManager = portManager;
        pktFifo = new HciQueue(1000);
    }

    // TODO: Remove this constructor
    /**
     * 
     */
    public PacketBuilder(final APHciSwingPanel autoPilot) {
        // TODO Auto-generated constructor stub
    }

    @Override
    public synchronized void serialEvent(final SerialPortEvent oEvent) {
        //logger.error("Im in Packet Builder");

        // Update the UI with the comms status after each 5 events
        commsCheckCtr++;
        if (commsCheckCtr > 5) {
            commsCheckCtr = 0;
            final SerialPortStatus portStatus = portManager.getPortStatus();
            setChanged();
            notifyObservers(portStatus);
        }

        if (oEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
            // Grab the data from the port.
            try {
                // Get number of bytes in InputStream
                final int available = getInput().available();

                // Ignore the first 50 bytes as they are usually corrupt.
                if (initcomms > 50) {
                    initcomms += available;
                    return;
                }

                final byte chunk[] = new byte[available];
                getInput().read(chunk, 0, available);
                
                for(Byte theByte: chunk){
                	currBuffer.add(theByte);
                }
                

                /*
                 * New foolproof way of getting valid packets
                 */
                //logger.info("before extract packet: " + available);
                logger.info("Data in: " + new String(chunk));
                byte[] byteArray = new byte[currBuffer.size()];
                for(int i = 0; i < currBuffer.size(); i++){
                	byteArray[i] = (byte)currBuffer.get(i);
                }
                logger.info("Raw data: " + new String(byteArray));
                final PacketInterface[] packets = PacketUtils
                        .extractPackets(byteArray);
                if(packets.length > 0){
                	currBuffer.removeAll(currBuffer);
                }
                logger.info("before fifo add: " + packets.length);
                logger.info("fifo length: " + pktFifo.size());
                for (int i = 0; i < packets.length; i++) {
                    //logger.info("In for loop: ");
                    pktFifo.add(packets[i]);
                }
                //logger.info("Out of loop: ");
                setChanged();
                //logger.info("Test1: ");
                notifyObservers(pktFifo.poll());
                //logger.info("Test2: ");
                //logger.info("pktFifo top item is: " + pktFifo.peek());

            } catch (final Exception e) {
                logger.error(e);
            }

        }
    }

    /**
     * 
     * @param tokens
     * @return true or false verify packet integrity by evaluating packet check
     *         sum.
     */
    public boolean checkSumPacket(final String[] tokens) {

        final String[] tokenArr = tokens;
        final int X = Integer.parseInt(tokenArr[1]);
        final int Y = Integer.parseInt(tokenArr[2]);
        final int Z = Integer.parseInt(tokenArr[3]);
        final int checkSum = Integer.parseInt(tokenArr[4]);
        final int sum = X + Y + Z;
        if (sum == checkSum) {
            return true;
        }
        return false;
    }

    /**
     * @return the input
     */
    public InputStream getInput() {
        return input;
    }

    /**
     * @param input
     *            the input to set
     */
    public void setInput(final InputStream input) {
        this.input = input;
    }

    /**
     * @return the output
     */
    public OutputStream getOutput() {
        return output;
    }

    /**
     * @param output
     *            the output to set
     */
    public void setOutput(final OutputStream output) {
        this.output = output;
    }

    public void setPortStatus(final SerialPortStatus portStatus) {
        this.portStatus = portStatus;
    }

}
